var assert  = require('assert'),
    utility = require('system/utility');

UtilityTests = (function () {
	var UtilityTests = function() {

		return {
			'#do()': {
				'exeuction should be asynchronous': function(done) {
					var a = 0;
					
					utility.do(
						function() {
							++a;
							assert.strictEqual(a, 2);
						},
						done
					);
					
					++a;
					assert.strictEqual(a, 1);
				},
				'execution order should be synchronous': function(done) {
					var a = 0;
					
					utility.do(
						function() {
							++a;
							assert.strictEqual(a, 1);
							
							process.nextTick(function() {
								assert.equal(a, 2);
							});
						},
						function() {
							var worker = this,
								done1  = worker.parallel(),
								done2  = worker.parallel();
							
							++a;
							assert.strictEqual(a, 2);
							
							process.nextTick(function() {
								++a;
								done1();
								
								process.nextTick(function() {
									++a;
									done2();
									
									process.nextTick(function() {
										assert.strictEqual(a, 5);
									});
								});
							});
							
						},
						function() {
							assert.strictEqual(a, 4);
							++a;
						},
						done
					);
				},
				'result and arguments should be passed to the next step': function(done) {
					utility.do(
						function() {
							return 'abc123';
						},
						function(previousResult) {
							assert.strictEqual(previousResult, 'abc123');
							
							var done1 = this.parallel();
							var done2 = this.parallel();
							
							process.nextTick(function() {
								done2(1, 2, 3);
							});
							
							process.nextTick(function() {
								done1('a', 'b', 'c');
							});
							
							return '123abc';
						},
						function(previousResult, worker1Result, worker2Result) {
							assert.strictEqual(previousResult, '123abc');
							assert.ok(utility.isArray(worker1Result));
							assert.deepEqual(worker1Result, ['a', 'b', 'c']);
							assert.deepEqual(worker2Result, [1, 2, 3]);
						},
						function()
						{
							assert.strictEqual(arguments.length, 1);
							assert.strictEqual(arguments[0], undefined);
						},
						done
					);
				},
				'repeat should repeat the step': function(done) {
					var a = 0;
					
					utility.do(
						function() {
							++a;
							
							if (a < 3) {
								this.repeat();
							}
						},
						function() {
							assert.equal(a, 3);
						},
						done
					);
				},
				'repeat should override other parallel finish calls': function(done) {
					var a = 0;
					
					utility.do(
						function() {
							if (a < 3) {
								var worker = this,
								    done1  = this.parallel(),
								    done2  = this.parallel();
								
								process.nextTick(function() {
									done1();
									
									process.nextTick(function() {
										worker.repeat();
										done2();
									});
								});
							}
							
							++a;
						},
						function() {
							assert.equal(a, 4);
						},
						done
					);
				},
				'calling the same parallel call twice should not proceed to the next step': function(done) {
					var done2Called = false;
					
					utility.do(
						function() {
							var done1 = this.parallel();
							var done2 = this.parallel();
							
							process.nextTick(function() {
								done1();
								done1();
								
								process.nextTick(function() {
									done2Called = true;
									done2(true);
								});
							});
						},
						function() {
							assert.ok(done2Called);
						},
						done
					);
				},
				'non-parallel calls should not proceed to the next step when stop is called': function(done) {
					var a = 0;
					
					utility.do(
						function() {
							this.stop();
							process.nextTick(function() {
								assert.equal(a, 0);
								done();
							});
						},
						function() {
							++a;
						}
					);
				},
				'parallel calls should not proceed to the next step when stop is called': function(done) {
					var a = 0;
					
					utility.do(
						function() {
							var worker = this,
							    done1  = this.parallel(),
							    done2  = this.parallel();
							
							process.nextTick(function() {
								done1();
								
								process.nextTick(function() {
									worker.stop();
									done2();
									
									process.nextTick(function() {
										assert.equal(a, 0);
										done();
									});
								});
							});
						},
						function() {
							++a;
							console.log('adding');
						}
					);
				}
			},
			'#extend([isDeep], target, source, [source]...)': {
				'basic object extending should not modify merge in objects' : function() {
					var settings = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" },
						options = { xnumber2: 1, xstring2: "x", xxx: "newstring" },
						optionsCopy = { xnumber2: 1, xstring2: "x", xxx: "newstring" },
						merged = { xnumber1: 5, xnumber2: 1, xstring1: "peter", xstring2: "x", xxx: "newstring" };
					
					utility.extend(settings, options);
					assert.deepEqual(settings, merged, "Check if extended: settings must be extended");
					assert.deepEqual(options, optionsCopy, "Check if not modified: options must not be modified");
					
					utility.extend(settings, null, options);
					assert.deepEqual(settings, merged, "Check if extended: settings must be extended");
					assert.deepEqual(options, optionsCopy, "Check if not modified: options must not be modified");
				},
				'deep extend of an object should clone child arrays' : function() {
					var arr = [1, 2, 3],
					    nestedarray = { arr: arr };
						
					assert.ok(utility.extend(true, {}, nestedarray).arr !== arr, "Deep extend of object must clone child array");
				},
				'cloned arrays should be arrays' : function() {
					var arr = [1, 2, 3],
					    nestedarray = { arr: arr };
						
					assert.ok(utility.isArray(utility.extend(true, { arr: {} }, nestedarray).arr ), "Cloned array heve to be an Array");
				},
				'cloned objects should be plain objects' : function() {
					var arr = [1, 2, 3];
					
					assert.ok(utility.isPlainObject(utility.extend(true, { arr: arr }, { arr: {} }).arr ), "Cloned object heve to be an plain object");
				},
				'the length property should copy' : function() {
					var empty = {};
					var optionsWithLength = { foo: { length: -1 } };
					utility.extend(true, empty, optionsWithLength);
					assert.deepEqual( empty.foo, optionsWithLength.foo, "The length property must copy correctly");
				},
				'dates should copy properly' : function() {
					var empty = {};
					var optionsWithDate = { foo: { date: new Date } };
					utility.extend(true, empty, optionsWithDate);
					assert.deepEqual( empty.foo, optionsWithDate.foo, "Dates copy correctly");
				},
				'custom objects should copy correctly' : function() {
					var myKlass = function() {},
					    customObject = new myKlass(),
					    optionsWithCustomObject = { foo: { date: customObject } },
					    empty = {};
					
					utility.extend(true, empty, optionsWithCustomObject);
					assert.ok(empty.foo && empty.foo.date === customObject, "Custom objects copy correctly (no methods)");
					
					// Makes the class a little more realistic
					myKlass.prototype = { someMethod: function(){} };
					empty = {};
					utility.extend(true, empty, optionsWithCustomObject);
					assert.ok(empty.foo && empty.foo.date === customObject, "Custom objects copy correctly");
				},
				'wrapped numbers should copy correctly' : function() {
					var ret = utility.extend(true, { foo: 4 }, { foo: new Number(5) } );
					assert.ok( ret.foo == 5, "Wrapped numbers copy correctly");
				},
				'values should be coppied correctly' : function() {
					var nullUndef,
					    options = { xnumber2: 1, xstring2: "x", xxx: "newstring" };
						
					nullUndef = utility.extend({}, options, { xnumber2: null });
					assert.ok(nullUndef.xnumber2 === null, "Check to make sure null values are copied");

					nullUndef = utility.extend({}, options, { xnumber2: undefined });
					assert.ok(nullUndef.xnumber2 === options.xnumber2, "Check to make sure undefined values are not copied");

					nullUndef = utility.extend({}, options, { xnumber0: null });
					assert.ok(nullUndef.xnumber0 === null, "Check to make sure null values are inserted");
				},
				'a recursive object should not go into a never-ending loop' : function() {
					var target = {};
					var recursive = { foo:target, bar:5 };
					utility.extend(true, target, recursive);
					assert.deepEqual( target, { bar:5 }, "Check to make sure a recursive obj doesn't go never-ending loop by not copying it over");
				},
				'values that are not equal with coersion should copy when necessary' : function() {
					var ret = utility.extend(true, { foo: [] }, { foo: [0] } ); // 1907
					assert.equal(ret.foo.length, 1, "Check to make sure a value with coersion 'false' copies over when necessary to fix #1907");
				},
				'values that are equal with coersion should overwrite correctly' : function() {
					var ret = utility.extend(true, { foo: "1,2,3" }, { foo: [1, 2, 3] } );
					assert.ok(typeof ret.foo != "string", "Check to make sure values equal with coersion (but not actually equal) overwrite correctly");
				},
				'a null value should not crash a deep extend' : function() {
					var ret = utility.extend(true, { foo:"bar" }, { foo:null } );
					assert.ok(typeof ret.foo !== "undefined", "Make sure a null value doesn't crash with deep extend, for #1908");
				},
				'a null value should be overwritten' : function() {
					var obj = { foo:null };
					utility.extend(true, obj, { foo:"notnull" } );
					assert.equal(obj.foo, "notnull", "Make sure a null value can be overwritten");
				},
				'you should be able to extend a function' : function() {
					function func() {};
					
					utility.extend(func, { key: "value" });
					assert.equal(func.key, "value", "Verify a function can be extended");
				},
				'you should be able to specifity multiple objects to extend with' : function() {
					var defaults = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" },
						defaultsCopy = { xnumber1: 5, xnumber2: 7, xstring1: "peter", xstring2: "pan" },
						options1 = { xnumber2: 1, xstring2: "x" },
						options1Copy = { xnumber2: 1, xstring2: "x" },
						options2 = { xstring2: "xx", xxx: "newstringx" },
						options2Copy = { xstring2: "xx", xxx: "newstringx" },
						merged2 = { xnumber1: 5, xnumber2: 1, xstring1: "peter", xstring2: "xx", xxx: "newstringx" };

					var settings = utility.extend({}, defaults, options1, options2);
					assert.deepEqual(settings, merged2, "Check if extended: settings must be extended");
					assert.deepEqual(defaults, defaultsCopy, "Check if not modified: options1 must not be modified");
					assert.deepEqual(options1, options1Copy, "Check if not modified: options1 must not be modified");
					assert.deepEqual(options2, options2Copy, "Check if not modified: options2 must not be modified");
				}
			},
			'#diff(source, target)' : {
				'if the target is a simple type it should just return the simple type' : function() {
					assert.equal(utility.diff(undefined, undefined), undefined);
					assert.equal(utility.diff(null, undefined), undefined);
					assert.equal(utility.diff(true, undefined), undefined);
					assert.equal(utility.diff(false, undefined), undefined);
					assert.equal(utility.diff(1, undefined), undefined);
					assert.equal(utility.diff('asdf', undefined), undefined);
					assert.equal(utility.diff(function() {}, undefined), undefined);
					assert.equal(utility.diff({ a: 'asdf' }, undefined), undefined);

					assert.equal(utility.diff(undefined, null), null);
					assert.equal(utility.diff(null, null), null);
					assert.equal(utility.diff(true, null), null);
					assert.equal(utility.diff(false, null), null);
					assert.equal(utility.diff(1, null), null);
					assert.equal(utility.diff('asdf', null), null);
					assert.equal(utility.diff(function() {}, null), null);
					assert.equal(utility.diff({ a: 'asdf' }, null), null);

					assert.equal(utility.diff(undefined, true), true);
					assert.equal(utility.diff(null, true), true);
					assert.equal(utility.diff(true, true), true);
					assert.equal(utility.diff(false, true), true);
					assert.equal(utility.diff(1, true), true);
					assert.equal(utility.diff('asdf', true), true);
					assert.equal(utility.diff(function() {}, true), true);
					assert.equal(utility.diff({ a: 'asdf' }, true), true);

					assert.equal(utility.diff(undefined, false), false);
					assert.equal(utility.diff(null, false), false);
					assert.equal(utility.diff(true, false), false);
					assert.equal(utility.diff(false, false), false);
					assert.equal(utility.diff(1, false), false);
					assert.equal(utility.diff('asdf', false), false);
					assert.equal(utility.diff(function() {}, false), false);
					assert.equal(utility.diff({ a: 'asdf' }, false), false);

					assert.equal(utility.diff(undefined, 1), 1);
					assert.equal(utility.diff(null, 1), 1);
					assert.equal(utility.diff(true, 1), 1);
					assert.equal(utility.diff(false, 1), 1);
					assert.equal(utility.diff(1, 1), 1);
					assert.equal(utility.diff('asdf', 1), 1);
					assert.equal(utility.diff(function() {}, 1), 1);
					assert.equal(utility.diff({ a: 'asdf' }, 1), 1);

					assert.equal(utility.diff(undefined, 'asdf'), 'asdf');
					assert.equal(utility.diff(null, 'asdf'), 'asdf');
					assert.equal(utility.diff(true, 'asdf'), 'asdf');
					assert.equal(utility.diff(false, 'asdf'), 'asdf');
					assert.equal(utility.diff(1, 'asdf'), 'asdf');
					assert.equal(utility.diff('asdf', 'asdf'), 'asdf');
					assert.equal(utility.diff(function() {}, 'asdf'), 'asdf');
					assert.equal(utility.diff({ a: 'asdf' }, 'asdf'), 'asdf');
				},
				'if the source is a simple type it should return a deep copy of the target' : function() {
					var result;
					var value;

					assert.notEqual(result = utility.diff(undefined, value = { a: 'asdf' }), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff(true, value = { a: 'asdf' }), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff(false, value = { a: 'asdf' }), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff(1, value = { a: 'asdf' }), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff('asdf', value = { a: 'asdf' }), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));


					assert.notEqual(result = utility.diff(undefined, value = [1,2,3,4]), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff(true, value = [1,2,3,4]), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff(false, value = [1,2,3,4]), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff(1, value = [1,2,3,4]), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));

					assert.notEqual(result = utility.diff('asdf', value = [1,2,3,4]), value);
					assert.equal(JSON.stringify(result), JSON.stringify(value));
				},
				'similar same the result should be an empty object' : function() {
					assert.deepEqual(utility.diff({ asdf : 1 }, { asdf : 1 }), {});
				},
				'objects that contain similar objects should not return the similar objects' : function() {
					assert.deepEqual(utility.diff({ a:1, b: { c: 'a' } }, { a:2, b: { c: 'a' } }), { a:2 });
					assert.deepEqual(utility.diff([{ a:1, b: { c: 'a' } }, 1], [{ a:2, b: { c: 'a' } }, 1]), [{ a:2 }]);
				},
				'the outcome should be the differences between the two objects' : function() {
					assert.deepEqual(utility.diff({ a:1, b: { } }, { a:2 }), { a:2, b: undefined });
					assert.deepEqual(utility.diff({ a:1 }, { a:2, b: { } }), { a:2, b: { } });
					assert.deepEqual(utility.diff({ a:1 }, { a:2, b: { c: 'a' } }), { a:2, b: { c: 'a' } });
					assert.deepEqual(utility.diff({ a:1, b: { c: 'a' } }, { a:2, b: { } }), { a:2, b: { c: undefined } });
					assert.deepEqual(utility.diff({ a:1, b: { c: 'a' } }, { a:2 }), { a:2, b: undefined });
				}
			},
			'#isPlainObject(obj)' : {
				'an empty object is a plain object' : function() {
					// The use case that we want to match
					assert.ok(utility.isPlainObject({}), "{}");
				},
				'non-prototype objects are plain objects' : function() {
					// The use case that we want to match
					assert.ok(utility.isPlainObject({ a: 1, b: function() {}, c: { a: 123 } }), "{}");
				},
				'simple types should not be objects' : function() {
					// Not objects shouldn't be matched
					assert.ok(!utility.isPlainObject(""), "string");
					assert.ok(!utility.isPlainObject(0) && !utility.isPlainObject(1), "number");
					assert.ok(!utility.isPlainObject(true) && !utility.isPlainObject(false), "boolean");
					assert.ok(!utility.isPlainObject(null), "null");
					assert.ok(!utility.isPlainObject(undefined), "undefined");
				},
				'arrays should not be plain objects' : function() {
					// Arrays shouldn't be matched
					assert.ok(!utility.isPlainObject([]), "array");
				},
				'dates should not be plain objects' : function() {
					// Instantiated objects shouldn't be matched
					assert.ok(!utility.isPlainObject(new Date), "new Date");
				},
				'functions should not plain objects' : function() {
					var fn = function(){};

					// Functions shouldn't be matched
					assert.ok(!utility.isPlainObject(fn), "fn");

					// Again, instantiated objects shouldn't be matched
					assert.ok(!utility.isPlainObject(new fn), "new fn (no methods)");

					// Makes the function a little more realistic
					// (and harder to detect, incidentally)
					fn.prototype = {someMethod: function(){}};

					// Again, instantiated objects shouldn't be matched
					assert.ok(!utility.isPlainObject(new fn), "new fn");
				},
			},
			'#isNumber(value)' : {
				'decimals are numbers' : function() {
					assert.ok(utility.isNumber(1.2345));
				},
				'integers are numbers' : function() {
					assert.ok(utility.isNumber(100));
				},
				'strings are not numbers' : function() {
					assert.ok(!utility.isNumber('1'));
				},
				'dates are not numbers' : function() {
					assert.ok(!utility.isNumber(new Date()));
				},
				'undefined is not a number' : function() {
					assert.ok(!utility.isNumber(undefined));
				},
				'null is not a number' : function() {
					assert.ok(!utility.isNumber(null));
				},
				'a function is not a number' : function() {
					assert.ok(!utility.isNumber(function() {}));
				},
				'an object is not a number' : function() {
					assert.ok(!utility.isNumber({}));
				},
				'arrays are not numbers' : function() {
					assert.ok(!utility.isNumber([1]));
				}
			},
			'#validateNumber(value)' : {
				'decimals are numbers' : function() {
					assert.strictEqual(utility.validateNumber(1.2345, 1.2345, 1.2345), 1.2345);
					assert.throws(function() { utility.validateNumber(1.9998, 1.9999); }, RangeError);
					assert.throws(function() { utility.validateNumber(1.9999, undefined, 1.9998); }, RangeError);
				},
				'integers are numbers' : function() {
					assert.strictEqual(utility.validateNumber(100, 100, 100), 100);
					assert.throws(function() { utility.validateNumber(1, 2); }, RangeError);
					assert.throws(function() { utility.validateNumber(2, undefined, 1); }, RangeError);
				},
				'strings are not numbers' : function() {
					assert.throws(function() { utility.validateNumber('1'); }, TypeError);
				},
				'dates are not numbers' : function() {
					assert.throws(function() { utility.validateNumber(new Date()); }, TypeError);
				},
				'undefined is not a number' : function() {
					assert.throws(function() { utility.validateNumber(undefined); }, TypeError);
				},
				'null is not a number' : function() {
					assert.throws(function() { utility.validateNumber(null); }, TypeError);
				},
				'a function is not a number' : function() {
					assert.throws(function() { utility.validateNumber(function() {}); }, TypeError);
				},
				'an object is not a number' : function() {
					assert.throws(function() { utility.validateNumber({}); }, TypeError);
				},
				'arrays are not numbers' : function() {
					assert.throws(function() { utility.validateNumber([1]); }, TypeError);
				}
			}
		}
	};

	return UtilityTests;
})();

// Hook into commonJS module systems
if (typeof module !== 'undefined' && "exports" in module) {
  module.exports = UtilityTests;
}